package app.domain.server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

import app.domain.order.Order;
import app.domain.server.heartbeat.ClientHeartbeatListener;
import app.domain.server.heartbeat.ServerHeartbeat;
import app.domain.server.util.ClientData;
import app.domain.server.util.MessageOutBox;
import app.domain.server.util.MessageParseSender;
import app.domain.util.Logger;

public class TcpServer {
	public static volatile int sequenceId = 0;
	private ServerSocket frontSocket;
	private Server server;

	public TcpServer(Server server) {
		this.server = server;
	}
	
	public void start(int port) {
		this.connect(port);
		while(true) {
			try {
				this.clientConnect();
			} catch (ClassNotFoundException | IOException e) {
				Logger.logServerError("cannot connect client");
			}
		}
	}
	
	private void connect(int port) {
		try {
			frontSocket = new ServerSocket(port);
		} catch (IOException e) {
			Logger.logServerError("Cannot connect to port: " + port);
		}
	}
	
	public void closeServer() {
		try {
			frontSocket.close();
		} catch (IOException e) {
			Logger.logServerError("Cannot close server");
		}
	}
	
	private void clientConnect() throws IOException, ClassNotFoundException {
		Logger.logServerInfo("available");
		Socket connection = frontSocket.accept();
		Logger.logServerInfo("Connection received from: " + connection.getInetAddress().getHostName());
		final ObjectInputStream in = new ObjectInputStream(connection.getInputStream());
		final ObjectOutputStream out = new ObjectOutputStream(connection.getOutputStream());
		final String clientId = MessageParseSender.login(this, in, out);
		if(clientId != null) {
			server.getClients().put(clientId, new ClientData(in,out,new MessageOutBox(),new ClientHeartbeatListener(this,clientId),new ServerHeartbeat(this,clientId)));
			startHeartbeating(clientId);
			new Thread(new Runnable() {
				@Override
				public void run() {
					while(!Thread.interrupted()) {
						clientListener(clientId);
					}
				}
			}).start();
			
			new Thread(new Runnable() {
				@Override
				public void run() {
					while(!Thread.interrupted()) {
						clientSender(clientId);
					}
				}
			}).start();
		}
	}
	
	private void startHeartbeating(String clientId) {
		server.getClients().get(clientId).getHeartListener().start();
		server.getClients().get(clientId).getServerHeartbeat().start();
	}
	
	private void clientSender(String clientId) {
		MessageOutBox messageOutBox = server.getClients().get(clientId).getMessageOutBox();
		sendMessage(server.getClients().get(clientId).getOut(), messageOutBox.remove());
	}
	
	private void clientListener(String clientId) {
		String msg = receiveMessage(clientId);
		if(msg == null) {
			clientDisconnect(clientId);
		} else {
			MessageParseSender.clientMessage(server,clientId,msg);
		}							
	}

	private String receiveMessage(String clientId) {
		String msg;
		try {
			msg = (String)server.getClients().get(clientId).getIn().readObject();
			Logger.logClientInfo(clientId, msg);
			return msg;
		} catch (ClassNotFoundException | IOException e) {
			Thread.currentThread().interrupt();
		}
		return null;			
	}
	
	public synchronized void sendMessage (String clientId, final String msg) {
		try {
			server.getClients().get(clientId).getOut().writeObject("sequenceId=" + (sequenceId++) + ";" + msg);
			server.getClients().get(clientId).getOut().flush();
			Logger.logServerInfo(msg);
		} catch (IOException e) {
			Thread.currentThread().interrupt();
		}
	}
	
	public void sendMessage (ObjectOutputStream out, final String msg) {
		try {
			synchronized(out) {
				out.writeObject("sequenceId=" + (sequenceId++) + ";" + msg);				
				out.flush();				
			}
			Logger.logServerInfo(msg);
		} catch (IOException e) {
			Thread.currentThread().interrupt();
		}
	}
	
	public void clientDisconnect(String clientId) {
		server.getClients().remove(clientId);
		Logger.logServerInfo("client " + clientId + " diconnected");
		this.server.getOrderQueue().add(Order.createDeleteClientOrder(clientId));
		Thread.currentThread().interrupt();
	}
	
	public Server getServer() {
		return server;
	}
	
}
